import sys, os
sys.path.append(os.path.abspath(os.path.join(os.getcwd(), '..')))

import stat, errno, psutil, calendar, io
from datetime import datetime
from functools import reduce

try: import _find_fuse_parts
except ImportError: pass

import fuse
from fuse import Fuse
fuse.fuse_python_api=(0,2)

class _Stat(fuse.Stat):
    def __init__(self):
        self.st_mode  = 0
        self.st_ino   = 0
        self.st_dev   = 0
        self.st_nlink = 0
        self.st_uid   = 0
        self.st_gid   = 0
        self.st_size  = 0
        self.st_atime = 0
        self.st_mtime = 0
        self.st_ctime = 0

class MemItem(object):
    def __init__(self, name, parent=None, **kwargs):
        print("MEMITEM:", name, parent)
        self._parent=None
        self._mode=kwargs.pop('mode',0o755)

        self.name=name
        self.parent=parent

        now=calendar.timegm(datetime.now().timetuple())
        self.creationTime=now
        self.lastAccessTime=now
        self.lastWriteTime=now
        self.fileAttributes=None

    @property
    def parent(self): return self._parent

    @parent.setter
    def parent(self, value):
        if self._parent==value: return

        if self._parent!=None: self._parent.children.remove(self)
        if value!=None: value.children.append(self)
        self._parent=value
    
    @property
    def name(self): return self._name

    @name.setter
    def name(self, value): self._name=value
    
    @property
    def creationTime(self): return self._creationTime

    @creationTime.setter
    def creationTime(self, value): self._creationTime=value
    
    @property
    def lastAccessTime(self): return self._lastAccessTime

    @lastAccessTime.setter
    def lastAccessTime(self, value): self._lastAccessTime=value
    
    @property
    def lastWriteTime(self): return self._lastWriteTime

    @lastWriteTime.setter
    def lastWriteTime(self, value): self._lastWriteTime=value    
  
    def fullPath(self):
        if self._parent==None: return '/'+self._name
        path=os.path.join(self._parent.fullPath(), self._name)
        return path.replace(os.sep, '/')

    def stat(self): raise NotImplementedError

class MemFolder(MemItem):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._children=[]

    @property
    def children(self): return self._children

    @children.setter
    def children(self, value):
        if self._children==value: return
        self._children=value

    @staticmethod
    def normpath(path):
        if path.isspace() or path=="": return "/"
        path=os.path.normpath(os.path.normcase(path))
        return path.replace(os.sep, '/')

    def getFolderByPath(self, path):
        path=MemFolder.normpath(path)
        fpath=MemFolder.normpath(self.fullPath())
        if path==fpath: return self

        for f in self._children:
            if not isinstance(f, MemFolder): continue
            child=f.getFolderByPath(path)
            if child!=None: return child
        return None     

    def createPath(self, path):
        pathParts=os.path.normpath(path).split('/')
        pathParts=list(filter(lambda s: not(s.isspace() or s==''), pathParts))
        if not len(pathParts): return

        newFolder=None
        for f in self._children:
            if not isinstance(c, MemFolder): continue

            fname=MemFolder.normpath(f.name)
            if fname==MemFolder.normpath(pathParts[0]):
                newFolder=f
                break

        if not newFolder: newFolder=MemFolder(pathParts[0], parent)

        if len(pathParts)>1:
            newFolder.createPath(path.replace(pathParts[0],""))

    @property
    def totalSize(self):
        sizes=[0]+[f.size for f in self._children if isinstance(f, MemFile)]
        total=reduce(lambda x,y: x+y, sizes)

        sizes=[0]+[f.totalSize for f in self._children if isinstance(f, MemFolder)]
        return total+reduce(lambda x,y: x+y, sizes)

    def fetchFile(self, name):
        print ("FETCH FILE:", name)
        name=MemFolder.normpath(name)
        print (name)
        for f in self._children:
            if not isinstance(f, MemFile): continue
            fname=MemFolder.normpath(f.name)
            print (fname)
            if name==fname:
                print ("RETURN", fname)
                return f
        return None

    def stat(self):
        s=_Stat()
        s.st_mode=stat.S_IFDIR | self._mode # U:rwx, G:rx, O:rx
        s.st_nlink=2
        s.st_atime=self._lastAccessTime
        s.st_mtime=self._lastWriteTime
        s.st_ctime=self._creationTime
        #s.st_size=8192# self.totalSize+4096
        return s

class MemFile(MemItem):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._content=io.BytesIO()

    @property
    def size(self): return self._content.getbuffer().nbytes

    @size.setter
    def size(self, value):
        self._content.truncate(value)
        if value==0: return
        self._content.seek(value-1)
        self._content.write(b'\x00')
        self._content.seek(0)

    def write(self, offset, data):
        self.size=offset+len(data)
        self._content.seek(offset)
        return self._content.write(data)

    def read(self, offset, length=1):
        self._content.seek(offset)
        return self._content.read(length)    

    def stat(self):
        s=_Stat()
        s.st_mode=stat.S_IFREG | self._mode # U:rwx, G:rx, O:rx
        s.st_nlink=1
        s.st_atime=self._lastAccessTime
        s.st_mtime=self._lastWriteTime
        s.st_ctime=self._creationTime
        s.st_size=self.size
        return s

class P3WFS(Fuse):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.parse(errex=1)
        self.fuse_args.mountpoint=P3WFS.getFreeMountPoint()
        self.fsname="DataShop"

        self._root=MemFolder('')

    @staticmethod
    def getFreeMountPoint():
        def crange(c1,c2):
            for c in range(ord(c1),ord(c2)+1): yield chr(c)

        used=[d.mountpoint for d in psutil.disk_partitions(all=True)]
        used=list(map(lambda d: d.replace(":","").replace(os.sep,""), used))
        for mp in crange("D","Z"):
            if mp not in used: return "{}:".format(mp)

    def statfs(self):
        """ Reimplement this to actually reflect contents! """
        class StatFS(object):
            def __init__(self, size): 
                memory=psutil.virtual_memory().available
                #memory=1024*1000*100
                self.f_bsize=4096        
                self.f_blocks=int(memory/self.f_bsize)
                self.f_bfree=self.f_blocks-100-(size//self.f_bsize)+1
        return StatFS(self._root.totalSize)

    def getattr(self, path):
        tfolder=self._root.getFolderByPath(path)
        if tfolder: return tfolder.stat()

        ppath,fname=os.path.split(path)
        pfolder=self._root.getFolderByPath(ppath)
        if pfolder:
            tfile=pfolder.fetchFile(fname)
            if tfile: return tfile.stat()

        # tfile=self._root.fetchFile(path)
        # if tfile: return tfile.stat()
        return -errno.ENOENT

    # Directory
    def mkdir(self, path, mode):
        try:
            ppath,name=os.path.split(path)
            parent=self._root.getFolderByPath(ppath)
            assert parent
            MemFolder(name,parent,mode=0o777)# mode)
            return 0
        except Exception as e:
            print(e)
            return -errno.ENOSYS

    def readdir(self, path, offset):
        for i in ('.','..'): yield fuse.Direntry(i)
        folder=self._root.getFolderByPath(path)
        assert folder
        for c in folder.children: yield fuse.Direntry(c.name)

    def rmdir(self, path):
        try:
            folder=self._root.getFolderByPath(path)
            folder.parent=None
            del folder
            return 0
        except exception as e:
            print (e)
            return -errno.ENOTEMPTY

    # File
    def create(self, path):
        try:
            ppath,name=os.path.split(path)
            parent=self._root.getFolderByPath(ppath)
            assert parent
            MemFile(name,parent,mode=0o777)
            return 0
        except Exception as e: raise

    def open(self, path, flags):
        accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR       

        if self.getattr(path)==-errno.ENOENT:
            if flags&accmode!=os.O_RDONLY:
                return self.create(path)
            return -errno.ENOENT
        
        #if (flags&accmode)!=os.O_RDONLY:
        #    return -errno.EACCES
        return 0

    def rename(self, oldPath, newPath):
        print ("RENAME", oldPath, newPath)

    def read(self, path, size, offset):
        try:
            ppath,name=os.path.split(path)
            parent=self._root.getFolderByPath(ppath)
            assert parent
            tfile=parent.fetchFile(name)
            assert tfile
            return tfile.read(offset, size)
        except Exception as e:
            print (e)
            return -errno.ENOENT

    def write(self, path, buf, offset):
        try:
            ppath,name=os.path.split(path)
            parent=self._root.getFolderByPath(ppath)
            assert parent
            tfile=parent.fetchFile(name)
            assert tfile
            return tfile.write(offset,buf)
            return 0
        except Exception as e:
            print (e)
            return -errno.ENOENT

    def unlink(self, path):
        print ("UNLINK", path)

    def release(self, *args, **kwargs): return 0o777

    def filetimes(self, path, created, accessed, modified, pInfo):
        print ("FILTETIMES:",path, created, accessed, modified, pInfo)
        if 1:
            ppath,name=os.path.split(path)
            parent=self._root.getFolderByPath(ppath)
            assert parent
            tfile=parent.fetchFile(name)
            assert tfile

            if created!=0: tfile.creationTime=created
            if accessed!=0: tfile.lastAccessTime=accessed
            if modified!=0: tfile.lastWriteTime=modified

            return 0

if __name__=="__main__":
    fs=P3WFS(
        version="%prog {0}".format(fuse.__version__),
        usage=Fuse.fusage,
        dash_s_do='setsingle',
        debug=0
    )
    fs.main()
